"""
@File    :   upload.py
@Time    :   2024/01/07 20:00:35
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   文件上传工具类
"""

from datetime import datetime
from pathlib import Path
from uuid import uuid1

import aiofiles
from fastapi import UploadFile

from src.business.admin.system.settings.schemas.out import (
    SystemSettingsCompleteOutSchema,
)
from src.config import get_app_settings
from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.utils.path import PathUtil

custom_config = get_app_settings()

upload_dir_path = PathUtil.get_upload_dir_path()


class UploadUtil:
    """文件上传工具类"""

    @staticmethod
    def get_or_create_date_dir_path(prefix_path: str) -> tuple[Path, Path]:
        """根据今天的日期获取上传文件夹的绝对和相对路径对象, 当对应文件夹不存在时进行创建
        Args:
            prefix_path: 基础路径
        Returns:
            tuple[str, str]:
                0: 绝对略经
                1: 相对路径
        """
        date_now = datetime.now().date()
        # 拼接完整的文件夹绝对路径
        resolve_folder_path = Path(
            custom_config.upload_dir_path,
            prefix_path,
            str(date_now.year),
            str(date_now.month),
            str(date_now.day),
        )
        if not resolve_folder_path.exists():
            # 文件夹不存在，创建文件夹
            resolve_folder_path.mkdir(parents=True, exist_ok=True)
        # 获取相对路径
        relative_folder_path = resolve_folder_path.relative_to(
            custom_config.upload_dir_path
        )
        return resolve_folder_path, relative_folder_path

    @staticmethod
    def get_file_suffix(filename: str, content_type: str = "") -> str:
        """获取文件扩展名
        Args:
            filename: 文件名称
            content_type: 文件内容类型
        Returns:
            str: 文件扩展名
        """
        content_type = content_type.strip()
        suffix = Path(filename).suffix
        if (not suffix) and content_type:
            suffix = content_type.split("/")[-1]
            if not suffix.startswith("."):
                suffix = "." + suffix
        return suffix

    @staticmethod
    def get_random_filename(file_suffix: str) -> str:
        """随机的文件名
        Args:
            file_suffix: 文件扩展名
        Returns:
            str: 随机的文件名称
        """
        random_filename = uuid1().hex
        # 当文件扩展名不为空时才会添加在随机的文件名后
        if file_suffix:
            random_filename += file_suffix
        return random_filename

    @staticmethod
    async def save_upload_file(
        upload_file: UploadFile,
        save_path: str | Path = upload_dir_path,
        random_filename: bool = True,
    ) -> str:
        """异步保存上传文件
        Args:
            upload_file: fastapi的上传文件对象
            save_path: 文件保存的基础文件夹的绝对路径
            random_filename: 将文件名随机化

        Returns:
            str: 保存的相对路径(相对upload文件夹)
        """
        # 将字符串统一转为Path对象, 方便进行路径处理
        if isinstance(save_path, str):
            save_path = Path(save_path)
        # 按日期创建创建
        save_path, _ = UploadUtil.get_or_create_date_dir_path(save_path.as_posix())
        # 随机化文件名
        if random_filename:
            # 获取文件扩展名
            file_suffix = UploadUtil.get_file_suffix(
                upload_file.filename, upload_file.content_type
            )
            # 获取随机的文件名称
            random_filename = UploadUtil.get_random_filename(file_suffix)
            # 构建最终的文件保存路径
            save_file_path = save_path.joinpath(random_filename)
        else:
            save_file_path = save_path.joinpath(upload_file.filename)

        # 异步保存文件
        async with aiofiles.open(save_file_path.as_posix(), "wb") as aio_f:
            while chunk := await upload_file.read(custom_config.upload_chunk_size):
                await aio_f.write(chunk)
        return save_file_path.relative_to(PathUtil.get_upload_dir_path()).as_posix()

    @staticmethod
    def validate_upload_file_suffix_and_size(
        upload_file: UploadFile,
        allow_suffix: list[str],
        allow_max_mb: int,
        raise_field_name: str,
    ):
        """验证上传文件是否符合租户要求

        Args:
            upload_file (UploadFile): 上传的文件对象
            allow_suffix (list[str]): 允许的文件名后缀
            allow_max_mb (int): 允许的最大MB
            raise_field_name (str): 报错字段
        """
        # 获取上传文件后缀
        upload_file_suffix = UploadUtil.get_file_suffix(
            upload_file.filename, upload_file.content_type
        )
        print(upload_file_suffix)
        if upload_file_suffix not in allow_suffix:
            # 拼接允许的文件名后缀用于报错
            allow_suffix_str = ", ".join(allow_suffix)
            raise generate_async_validator_exception(
                raise_field_name,
                "上传文件的后缀名不符合要求, 只能是[ "
                + allow_suffix_str
                + " ]中的一种",
            )
        if upload_file.size is None:
            raise generate_async_validator_exception(
                raise_field_name, "上传文件大小为空"
            )
        # 将MB转为B
        allow_max_b = allow_max_mb * 1024 * 1024
        if upload_file.size > allow_max_b:
            raise generate_async_validator_exception(
                raise_field_name, "上传文件大小应小于" + str(allow_max_mb) + "MB"
            )

    @staticmethod
    async def validate_and_save_upload_file(
        upload_file: UploadFile,
        allow_suffix: list[str],
        allow_max_mb: int,
        raise_field_name: str,
    ) -> str:
        """验证并保存上传文件

        Args:
            upload_file (UploadFile): 上传文件对象
            allow_suffix (list[str]): 允许的文件名后缀
            allow_max_mb (int): 允许的最大MB
            raise_field_name (str): 报错字段

        Returns:
            str: 保存的相对路径(相对upload文件夹)
        """
        # 验证上传文件的合规性
        UploadUtil.validate_upload_file_suffix_and_size(
            upload_file, allow_suffix, allow_max_mb, raise_field_name
        )
        # 保存上传文件
        return await UploadUtil.save_upload_file(upload_file)
